﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;


namespace Stonecode.Solar.ParticleEngineTest
{
    public enum CameraType
    {
        PlayerCamera, ThirdPersonCamera
    };

    class Camera
    {
#region Private Variables

        private Vector3 _cameraPosition;
        private Vector3 _cameraTarget;
        private Vector3 _upDirection;
        private float _nearPlane;
        private float _farPlane;
        private Vector2 _size;
        private float _fieldOfView;
        private float _aspectRatio;
        private string _name;
        private Matrix _view, _proj, _viewproj;

#endregion

        #region Public Properties

        public Vector3 Position
        {
            get { return _cameraPosition; }
            set 
            { _cameraPosition = value; }
        }

        public Vector3 Target
        {
            get { return _cameraTarget; }
            set { _cameraTarget = value; }
        }

        public float NearPlane
        {
            get { return _nearPlane; }
            set 
            {     
                _nearPlane = (float)Math.Max(1f, value); 
            }
        }

        public float FarPlane
        {
            get { return _farPlane; }
            set
            {
                _farPlane = (float)Math.Max(5f, value);
            }
        }

        public Vector2 ViewportSize
        {
            get { return _size; }
            set
            {
                _size = value;
                _aspectRatio = _size.Y / _size.X;
            }
        }

        public float FieldOfView
        {
            get { return _fieldOfView; }
            set { _fieldOfView = value; }
        }

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        public Vector3 UpDirection
        {
            get { return _upDirection; }
            set { _upDirection = value; }
        }

#endregion

        #region Public ReadOnly Properties

        public float AspectRatio
        {
            get { return _aspectRatio; }
            set 
            { 
                _aspectRatio = value;
                UpdateProjectionMatrix();
            }
        }

        public Matrix ViewProjMatrix
        {
            //get { return this.ViewMatrix * this.ProjectionMatrix; }
            get { return this._viewproj; }
        }

        public Matrix ViewMatrix
        {
            get 
            { 
                //return Matrix.CreateLookAt(_cameraPosition, _cameraTarget, 
                //_upDirection); 
                return _view;
            }
        }

        public Matrix ProjectionMatrix
        {
            get 
            {
                //return Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(_fieldOfView), 
                //    _aspectRatio, _nearPlane, _farPlane); 
                return _proj;
            }
        }

#endregion

#region Constructors
        /// <summary>
        /// Creates a new camera with default values
        /// </summary>
        public Camera(Vector2 vportSize, string cameraName)
        {
            this.Position = new Vector3(0f, 0f, -30f);
            this.Target = Vector3.Zero;
            this.ViewportSize = vportSize;
            this.NearPlane = 1f;
            this.FarPlane = 1000f;
            this.FieldOfView = MathHelper.ToRadians(45f);
            this.Name = cameraName;
            this._upDirection = Vector3.Up;
        }


#endregion

#region Public Methods

        /// <summary>
        /// Strafes the camera Right with a default multiplier of 1f
        /// </summary>
        public void StrafeCameraRight()
        {
            StrafeCameraRight(1f);
        }
        /// <summary>
        /// Strafes the camera Right with a multiplier
        /// </summary>
        /// <param name="multiplier"></param>
        public void StrafeCameraRight(float multiplier)
        {
            // rotate the lookat point -90deg on the Y axis
            Matrix rotMat = Matrix.CreateRotationY((3f * (float)Math.PI) / 2f);
            Vector3 diff = _cameraTarget - _cameraPosition;
            Vector3.Transform(ref diff, ref rotMat, out diff);

            diff.Normalize();
            diff *= multiplier;
            MoveCameraAndLookat(_cameraPosition + diff);
        }

        /// <summary>
        /// Strafes the camera Left with a default multiplier of 1f
        /// </summary>
        public void StrafeCameraLeft()
        {
            StrafeCameraLeft(1f);
        }
        /// <summary>
        /// Strafes the camera Left with a multiplier
        /// </summary>
        /// <param name="multiplier"></param>
        public void StrafeCameraLeft(float multiplier)
        {
            // rotate the lookat point 90deg on the Y axis
            Matrix rotMat = Matrix.CreateRotationY((float)Math.PI / 2f);
            Vector3 diff = _cameraTarget - _cameraPosition;
            Vector3.Transform(ref diff, ref rotMat, out diff);
            
            diff.Normalize();
            diff *= multiplier;
            MoveCameraAndLookat(_cameraPosition + diff);
        }

        /// <summary>
        /// Moves both the Camera and Target by the amount given in
        /// direction.
        /// </summary>
        /// <param name="direction">The amount to move the camera and target</param>
        public void MoveCameraAndLookat(Vector3 direction)
        {
            //Vector3 diff = newCameraPos - _cameraPosition;
            //_cameraPosition = newCameraPos;
            //_cameraTarget += diff;
            _cameraPosition +=  direction;
            _cameraTarget   +=  direction;
            //UpdateMatricies();
            UpdateViewMatrix();
        }

        /// <summary>
        /// Rotates the camera around the target
        /// </summary>
        /// <param name="rot2d">rotation in degrees</param>
        public void RotateCameraAroundLookAt(Vector2 rot2d)
        {
            Matrix rotMat = Matrix.CreateRotationX(MathHelper.ToRadians(rot2d.X)) *
                Matrix.CreateRotationY(MathHelper.ToRadians(rot2d.Y));

            Vector3 resetPo = _cameraPosition - _cameraTarget;
            Vector3.Transform(ref resetPo, ref rotMat, out resetPo);
            Vector3 newPos = resetPo + _cameraTarget;

            // Test if we are at the max rotation up or down
            if ((_cameraPosition.X < _cameraTarget.X && newPos.X >= _cameraTarget.X) ||
                (_cameraPosition.Y < _cameraTarget.Y && newPos.Y >= _cameraTarget.Y) ||
                (_cameraPosition.Z < _cameraTarget.Z && newPos.Z >= _cameraTarget.Z) ||
                (_cameraPosition.X > _cameraTarget.X && newPos.X <= _cameraTarget.X) ||
                (_cameraPosition.Y > _cameraTarget.Y && newPos.Y <= _cameraTarget.Y) ||
                (_cameraPosition.Z > _cameraTarget.Z && newPos.Z <= _cameraTarget.Z))
            { return; }

            _cameraPosition = newPos;
            //UpdateMatricies();
            UpdateViewMatrix();
        }

        /// <summary>
        /// Rotates the Camera Target around the CameraPosition
        /// </summary>
        /// <param name="rot">rotation in degrees</param>
        public void RotateView(Vector2 rot)
        {
            Matrix rotMat = Matrix.CreateRotationX(MathHelper.ToRadians(rot.X)) *
                Matrix.CreateRotationY(MathHelper.ToRadians(rot.Y));

            Vector3 resetPo = _cameraTarget - _cameraPosition;
            Vector3.Transform(ref resetPo, ref rotMat, out resetPo);
            _cameraTarget += _cameraPosition;
            //UpdateMatricies();
            UpdateViewMatrix();
        }


        /// <summary>
        /// Moves the camera closer to the CameraTarget
        /// </summary>
        /// <param name="multiplier"></param>
        public void ZoomCamera(float multiplier)
        {
            Vector3 direction = _cameraTarget - _cameraPosition;
            direction.Normalize();

            // Check to see if we are going to be past the CameraTarget
            // If so then exit
            Vector3 newPos = _cameraPosition + (direction * multiplier);
            if (newPos == _cameraTarget)
                return;
            if ((_cameraPosition.X < _cameraTarget.X && newPos.X > _cameraTarget.X) ||
                (_cameraPosition.Y < _cameraTarget.Y && newPos.Y > _cameraTarget.Y) ||
                (_cameraPosition.Z < _cameraTarget.Z && newPos.Z > _cameraTarget.Z) ||
                (_cameraPosition.X > _cameraTarget.X && newPos.X < _cameraTarget.X) ||
                (_cameraPosition.Y > _cameraTarget.Y && newPos.Y < _cameraTarget.Y) ||
                (_cameraPosition.Z > _cameraTarget.Z && newPos.Z < _cameraTarget.Z))
            { return; }

            _cameraPosition += (direction * multiplier);
            //UpdateMatricies();
            UpdateViewMatrix();
        }

        public void UpdateMatricies()
        {
            UpdateViewMatrix();
            UpdateProjectionMatrix();
        }
        

#endregion

        #region Private Methods

        private void UpdateViewMatrix()
        {
            Matrix.CreateLookAt(ref _cameraPosition, ref _cameraTarget, ref _upDirection, out _view);
            UpdateViewProjMatrix();
        }
        private void UpdateProjectionMatrix()
        {
            if (_nearPlane > 0 && _farPlane > 0 && _aspectRatio > 0 && _fieldOfView > 0)
            {   
                Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(_fieldOfView),
                _aspectRatio, _nearPlane, _farPlane, out _proj);
                //Matrix.CreateOrthographic(this.ViewportSize.X, this.ViewportSize.Y, _nearPlane, _farPlane, out _proj);

                //float width = this.AspectRatio * 25f;
                //Matrix.CreateOrthographic(this.AspectRatio * 25, 25, _nearPlane, _farPlane, out _proj);
                UpdateViewProjMatrix();
            }
        }
        private void UpdateViewProjMatrix()
        {
            if (_view != null && _proj != null)
                Matrix.Multiply(ref _view, ref _proj, out _viewproj);
        }

        #endregion

    }
}
